Fix cargo test --doc with dev-deps
authorAlex Crichton <alex@alexcrichton.com>
Tue, 20 Dec 2016 00:28:06 +0000 (16:28 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Thu, 5 Jan 2017 17:34:57 +0000 (09:34 -0800)
Previously Cargo accidentally didn't pull in dev-dependencies due to the way
`cargo test --doc` was interpreted in terms of top-level targets. This PR
special cases this situation by ensuring that the doctest intention makes its
way all to the backend and the dependencies can be correctly calculated.

Closes #3422

src/bin/test.rs
src/cargo/core/manifest.rs
src/cargo/core/workspace.rs
src/cargo/ops/cargo_clean.rs
src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_rustc/context.rs
src/cargo/ops/cargo_rustc/job_queue.rs
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/util/toml.rs
tests/test.rs

index e10684dd9b0877c403fecbdd1d375194435324ae..59bf0a09d1fae21aad98d3e1d8c8adec367512c2 100644 (file)
@@ -105,7 +105,7 @@ pub fn execute(options: Options, config: &Config) -> CliResult<Option<()>> {
     let empty = Vec::new();
     let (mode, filter);
     if options.flag_doc {
-        mode = ops::CompileMode::Build;
+        mode = ops::CompileMode::Doctest;
         filter = ops::CompileFilter::new(true, &empty, &empty, &empty, &empty);
     } else {
         mode = ops::CompileMode::Test;
index 86cd6eebd1e57850161806a6d1ae11bd32e5db45..530fffc7f8a2d48f428fd05d9352a5ff0e5ee0a6 100644 (file)
@@ -170,6 +170,7 @@ pub struct Profiles {
     pub doc: Profile,
     pub custom_build: Profile,
     pub check: Profile,
+    pub doctest: Profile,
 }
 
 /// Information about a binary, a library, an example, etc. that is part of the
@@ -535,6 +536,14 @@ impl Profile {
             ..Profile::default_dev()
         }
     }
+
+    pub fn default_doctest() -> Profile {
+        Profile {
+            doc: true,
+            test: true,
+            ..Profile::default_dev()
+        }
+    }
 }
 
 impl Default for Profile {
index 870733f96d9992d0734aca4a6ca2398a96d3ad39..afdb047ca99ddde2f8b33f82e6cf82ba6b9b7237 100644 (file)
@@ -461,6 +461,7 @@ impl<'cfg> Workspace<'cfg> {
                 doc: Profile::default_doc(),
                 custom_build: Profile::default_custom_build(),
                 check: Profile::default_check(),
+                doctest: Profile::default_doctest(),
             };
 
             for pkg in self.members().filter(|p| p.manifest_path() != root_manifest) {
index 9627c902154eff880d681965d0a5f3c47928491e..b7c214a75d9da54328593c45f403c33e11971c99 100644 (file)
@@ -51,10 +51,11 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> {
             for kind in [Kind::Host, Kind::Target].iter() {
                 let Profiles {
                     ref release, ref dev, ref test, ref bench, ref doc,
-                    ref custom_build, ref test_deps, ref bench_deps, ref check
+                    ref custom_build, ref test_deps, ref bench_deps, ref check,
+                    ref doctest,
                 } = *profiles;
                 let profiles = [release, dev, test, bench, doc, custom_build,
-                                test_deps, bench_deps, check];
+                                test_deps, bench_deps, check, doctest];
                 for profile in profiles.iter() {
                     units.push(Unit {
                         pkg: &pkg,
index 1be87408ab426ec8202ab58e188d337e8ca814cc..3ca2315129c813651bc8acf947d62c00f30fd315 100644 (file)
@@ -69,6 +69,7 @@ pub enum CompileMode {
     Check,
     Bench,
     Doc { deps: bool },
+    Doctest,
 }
 
 #[derive(Clone, Copy, PartialEq, Eq, RustcDecodable)]
@@ -290,6 +291,7 @@ fn generate_targets<'a>(pkg: &'a Package,
         CompileMode::Build => build,
         CompileMode::Check => &profiles.check,
         CompileMode::Doc { .. } => &profiles.doc,
+        CompileMode::Doctest => &profiles.doctest,
     };
     match *filter {
         CompileFilter::Everything => {
@@ -329,6 +331,15 @@ fn generate_targets<'a>(pkg: &'a Package,
                     Ok(pkg.targets().iter().filter(|t| t.documented())
                           .map(|t| (t, profile)).collect())
                 }
+                CompileMode::Doctest => {
+                    if let Some(t) = pkg.targets().iter().find(|t| t.is_lib()) {
+                        if t.doctested() {
+                            return Ok(vec![(t, profile)])
+                        }
+                    }
+
+                    Ok(Vec::new())
+                }
             }
         }
         CompileFilter::Only { lib, bins, examples, tests, benches } => {
index a5a16b4791d672411cf6cfca1945ef58f6bf9583..f2f5ef39e37936e208364d309e363b8cfa8252cb 100644 (file)
@@ -224,7 +224,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
 
             map.insert(crate_type.to_string(), Some((prefix.to_string(), suffix.to_string())));
         }
+
         let cfg = if has_cfg {
             Some(try!(lines.map(Cfg::from_str).collect()))
         } else {
@@ -554,7 +554,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
     pub fn dep_targets(&self, unit: &Unit<'a>) -> CargoResult<Vec<Unit<'a>>> {
         if unit.profile.run_custom_build {
             return self.dep_run_custom_build(unit)
-        } else if unit.profile.doc {
+        } else if unit.profile.doc && !unit.profile.test {
             return self.doc_deps(unit);
         }
 
@@ -626,7 +626,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
         // the library of the same package. The call to `resolve.deps` above
         // didn't include `pkg` in the return values, so we need to special case
         // it here and see if we need to push `(pkg, pkg_lib_target)`.
-        if unit.target.is_lib() {
+        if unit.target.is_lib() && !unit.profile.doc {
             return Ok(ret)
         }
         ret.extend(self.maybe_lib(unit));
index f06a0bbebb6b1c25637c4e0128ead022b0840bd4..0188330b99419746b9e0894efa65f02f5870e388 100644 (file)
@@ -292,8 +292,10 @@ impl<'a> JobQueue<'a> {
             // being a compiled package
             Dirty => {
                 if key.profile.doc {
-                    self.documented.insert(key.pkg);
-                    config.shell().status("Documenting", key.pkg)?;
+                    if !key.profile.test {
+                        self.documented.insert(key.pkg);
+                        config.shell().status("Documenting", key.pkg)?;
+                    }
                 } else {
                     self.compiled.insert(key.pkg);
                     config.shell().status("Compiling", key.pkg)?;
index 24c3de65697ace7eca423bb0e9058bbe8829c8d9..0c9ea9decb262b0d29647ec8adcc59a7384298d3 100644 (file)
@@ -13,6 +13,7 @@ use core::{Profile, Profiles, Workspace};
 use core::shell::ColorConfig;
 use util::{self, CargoResult, ProcessBuilder, human, machine_message};
 use util::{Config, internal, ChainError, profile, join_paths, short_hash};
+use util::Freshness;
 
 use self::job::{Job, Work};
 use self::job_queue::JobQueue;
@@ -183,6 +184,9 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>,
 
     let (dirty, fresh, freshness) = if unit.profile.run_custom_build {
         custom_build::prepare(cx, unit)?
+    } else if unit.profile.doc && unit.profile.test {
+        // we run these targets later, so this is just a noop for now
+        (Work::new(|_| Ok(())), Work::new(|_| Ok(())), Freshness::Fresh)
     } else {
         let (freshness, dirty, fresh) = fingerprint::prepare_target(cx, unit)?;
         let work = if unit.profile.doc {
index b708d18cf9d09beb018c923e77384d145ea7991b..ecfcd0fcf8386f9c5a4b46f3ea429f1279a5546f 100644 (file)
@@ -1253,6 +1253,7 @@ fn build_profiles(profiles: &Option<TomlProfiles>) -> Profiles {
         custom_build: Profile::default_custom_build(),
         check: merge(Profile::default_check(),
                      profiles.and_then(|p| p.dev.as_ref())),
+        doctest: Profile::default_doctest(),
     };
     // The test/bench targets cannot have panic=abort because they'll all get
     // compiled with --test which requires the unwind runtime currently
index 1ed25b6f0b6796793352f8f8294ad002e213d77d..37fafddb64c91f7537aaddadc7ea7dde4bd9a3cd 100644 (file)
@@ -2143,6 +2143,7 @@ fn only_test_docs() {
             }
 
             /// ```
+            /// foo::bar();
             /// println!("ok");
             /// ```
             pub fn bar() {
@@ -2524,3 +2525,35 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
 
 "));
 }
+
+#[test]
+fn doctest_only_with_dev_dep() {
+    let p = project("workspace")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "a"
+            version = "0.1.0"
+
+            [dev-dependencies]
+            b = { path = "b" }
+        "#)
+        .file("src/lib.rs", r#"
+            /// ```
+            /// extern crate b;
+            ///
+            /// b::b();
+            /// ```
+            pub fn a() {}
+        "#)
+        .file("b/Cargo.toml", r#"
+            [project]
+            name = "b"
+            version = "0.1.0"
+        "#)
+        .file("b/src/lib.rs", r#"
+            pub fn b() {}
+        "#);
+
+    assert_that(p.cargo_process("test").arg("--doc").arg("-v"),
+                execs().with_status(0));
+}